#include <mono/common/file.h>
#include <mono/common/path.h>
#include <mono/common/buffer.h>
#include <mono/common/directory_iterator.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <algorithm>
#include <errno.h>
#include <stdio.h>
#include <utime.h>
#include <cstring>
#  pragma GCC diagnostic ignored "-Wshadow"

namespace mono {
File::File()
{
	_rc = 0;
}

int  File::init(const std::string &path)
{
	_path = path;
	std::string::size_type n = _path.size();
	if (n > 1 && _path[n - 1] == '/'){
		_path.resize(n - 1);
	}
	return 0;
}

File::File(const std::string& path)
{
	_rc = init(path);
}


File::File(const char* path)
{
	_rc = init(std::string(path));
}


File::File(const Path& path)
{
	_rc = init(std::string(path.toString()));
}


File::File(const File& file)
{
	_rc = init(std::string(file._path));	
}


File::~File()
{
}


File& File::operator = (const File& file)
{
	_rc = init(std::string(file._path));	
	return *this;
}


File& File::operator = (const std::string& path)
{
	_rc = init(path);
	return *this;
}


File& File::operator = (const char* path)
{
	_rc = init(std::string(path));
	return *this;
}


File& File::operator = (const Path& path)
{
	_rc = init(path.toString());
	return *this;
}


void File::swap(File& file)
{
	std::swap(_path, file._path);
	_rc = file._rc;
}


bool File::exists() const
{
	//assert(!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct stat st;
	return stat(_path.c_str(), &st) == 0;
}

	
bool File::canRead() const
{
	//assert(!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct stat st;
	if (stat(_path.c_str(), &st) == 0)
	{
		if (st.st_uid == geteuid())
			return (st.st_mode & S_IRUSR) != 0;
		else if (st.st_gid == getegid())
			return (st.st_mode & S_IRGRP) != 0;
		else
			return (st.st_mode & S_IROTH) != 0 || geteuid() == 0;
	}

	return false;

}

	
bool File::canWrite() const
{
	//assert(!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct stat st;
	if (stat(_path.c_str(), &st) == 0) {
		if (st.st_uid == geteuid())
			return (st.st_mode & S_IWUSR) != 0;
		else if (st.st_gid == getegid())
			return (st.st_mode & S_IWGRP) != 0;
		else
			return (st.st_mode & S_IWOTH) != 0 || geteuid() == 0;
	}
	return false;
}


bool File::canExecute() const
{
	//assert(!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct stat st;
	if (stat(_path.c_str(), &st) == 0) {
		if (st.st_uid == geteuid() || geteuid() == 0)
			return (st.st_mode & S_IXUSR) != 0;
		else if (st.st_gid == getegid())
			return (st.st_mode & S_IXGRP) != 0;
		else
			return (st.st_mode & S_IXOTH) != 0;
	}

	return false;
}


bool File::isFile() const
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct stat st;
	if (stat(_path.c_str(), &st) == 0)
		return S_ISREG(st.st_mode);

	return false;
}

	
bool File::isDirectory() const
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct stat st;
	if (stat(_path.c_str(), &st) == 0)
		return S_ISDIR(st.st_mode);
	return false;
}


bool File::isLink() const
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct stat st;
	if (lstat(_path.c_str(), &st) == 0)
		return S_ISLNK(st.st_mode);
	return false;
}


bool File::isDevice() const
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct stat st;
	if (stat(_path.c_str(), &st) == 0) {
		return S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode);
	}
	return false;
}


bool File::isHidden() const
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	Path p(_path);
	p.makeFile();

	return p.getFileName()[0] == '.';
}


Timestamp File::created() const
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return 0;
	}
	struct stat st;
	if (stat(_path.c_str(), &st) == 0) {
		return Timestamp::fromEpochTime(st.st_ctime);
	}
	_rc = errno;
	return 0;
}

	
Timestamp File::getLastModified() const
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct stat st;
	if (stat(_path.c_str(), &st) == 0) {
		return Timestamp::fromEpochTime(st.st_mtime);
	}
	_rc = errno;
	return 0;
}

	
bool File::setLastModified(const Timestamp& ts)
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct utimbuf tb;
	tb.actime  = ts.epochTime();
	tb.modtime = ts.epochTime();
	if(utime(_path.c_str(), &tb) != 0) {
		_rc = errno;
		return false;
	}
	return true;
}

	
File::FileSize File::getSize() const
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return 0;
	}
	struct stat st;
	if (stat(_path.c_str(), &st) == 0) {
		return st.st_size;
	}
	_rc = errno;
	return 0;
}

	
bool File::setSize(FileSize size)
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	if(::truncate(_path.c_str(), size) != 0) {
		_rc = errno;
		return false;
	}
	return true;
}

	
bool File::setWriteable(bool flag)
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct stat st;
	::stat(_path.c_str(), &st); 
	mode_t mode;
	if (flag) {
		mode = st.st_mode | S_IWUSR;
	} else {
		mode_t wmask = S_IWUSR | S_IWGRP | S_IWOTH;
		mode = st.st_mode & ~wmask;
	}
	if(::chmod(_path.c_str(), mode) != 0) {
		_rc = errno;
		return false;
	} 
	return true;
}


bool File::setReadOnly(bool flag)
{
	if(_path.empty()) {
		return false;
	}
	return setWriteable(!flag);
}	


bool File::setExecutable(bool flag)
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	struct stat st;
	if (stat(_path.c_str(), &st) != 0) { 
		_rc = errno;
		return false;
	}
	mode_t mode;
	if (flag) {
		mode = st.st_mode | S_IXUSR;
	} else {
		mode_t wmask = S_IXUSR | S_IXGRP | S_IXOTH;
		mode = st.st_mode & ~wmask;
	}
	if(::chmod(_path.c_str(), mode)!= 0) {
		_rc = errno;
		return false;
	}
	return true;
}

	
bool  File::copyTo(const std::string& path) const
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	Path src(_path);
	Path dest(path);
	File destFile(path);
	if ((destFile.exists() && destFile.isDirectory()) || dest.isDirectory())
	{
		dest.makeDirectory();
		dest.setFileName(src.getFileName());
	}
	if (isDirectory()) {
		return copyDirectory(dest.toString());
	} 

	return copyFile(dest.toString());
	
}

bool File::readToString(std::string &content, File::FileSize limited)
{
	content.clear();
	File::FileSize fsize = getSize();
	if (fsize == 0) {
		return false;
	} 
	if(fsize > limited) {
		fsize = limited;
	}

	content.reserve(fsize);
	int fd = ::open(_path.c_str(),O_RDONLY|O_CLOEXEC);

	if(fd == -1) {
		::close(fd);
		return false;
	}

	char buff[4096];
	size_t hasread = 0;
	while(hasread < fsize) {
		size_t nread = fsize - hasread > sizeof(buff) ? sizeof(buff) : fsize - hasread;
		ssize_t n = ::read(fd,buff,nread);
		if(n <= 0) {
			::close(fd);	
			return false;
		}
		content.append(buff, n);
		hasread += n;
	}
	::close(fd);
	return true;
}
	
bool File::writeToFile(const char* content, FileSize size)
{
	if(!content) {
		return false;
	}

	int fd = ::open(_path.c_str(),O_WRONLY|O_CLOEXEC|O_CREAT|O_TRUNC, 0644);

	if(fd == -1) {
		::close(fd);
		return false;
	}

	//just create a file
	if(size == 0) {
		::close(fd);
		return true;
	}

	ssize_t n = ::write(fd, content, size);
	::close(fd);
	if(n <= 0 || static_cast<size_t>(n) != size) {	
		return false;
	}
	
	return true;
}

bool File::writeToFile(std::string &content)
{
	return writeToFile(content.c_str(), content.size());
}
bool File::copyFile(const std::string& path) const
{

	//assert (!_path.empty());
	//assert (!path.empty());
	if(_path.empty()) {
		return false;
	}
	if(path.empty()) {
		return false;
	}
	bool broken = false;
	int sd = open(_path.c_str(), O_RDONLY);
	if (sd == -1) {
		_rc = errno;
		return false;
	}

	struct stat st;
	if (fstat(sd, &st) != 0)  {
		close(sd);
		_rc = errno;
		return false;
	}
	const long blockSize = st.st_blksize;

	int dd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, st.st_mode);
	if (dd == -1) {
		close(sd);
		_rc = errno;
		return false;
	}
	Buffer<char> buffer(blockSize);
	int n;
	while ((n = read(sd, buffer.begin(), blockSize)) > 0) {
		if (write(dd, buffer.begin(), n) != n)  {
			broken = true;
		}
			
	}
	::close(sd);
	if (n < 0 || broken == true) {
		_rc = errno;
		::close(dd);	
		return false;
	}
	if (fsync(dd) != 0) {
		close(dd);
		return false;
	}
	::close(dd);
	return true;
}


bool File::copyDirectory(const std::string& path) const
{
	if(_path.empty()) {
		return false;
	}
	if(path.empty()) {
		return false;
	}
	File target(path);
	target.createDirectories();

	Path src(_path);
	src.makeFile();
	DirectoryIterator it(src);
	DirectoryIterator end;
	for (; it != end; ++it) {
		bool ret = it->copyTo(path);
		if(!ret) {
			return false;
		}
	}
	return true;
}


bool File::moveTo(const std::string& path)
{
	if(_path.empty()) {
		return false;
	}
	if(path.empty()) {
		return false;
	}
	bool ret = false;
	if(renameTo(path)) {
		ret =  true;
	} else if(copyTo(path)){	
		ret = remove(true);
	}
	
	if(ret) {
		_rc = init(path);
	}
	return ret;
}

	
bool File::renameTo(const std::string& path)
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	
	if(path.empty()) {
		return false;
	}
	if (rename(_path.c_str(), path.c_str()) != 0) {
		_rc = errno;
		return false;
	}
	_rc = init(path);
	return true;
}

	
bool File::remove(bool recursive)
{
	if(_path.empty()) {
		return false;
	}

	if (recursive && !isLink() && isDirectory()) {
		std::vector<File> files;
		list(files);
		for (std::vector<File>::iterator it = files.begin(); it != files.end(); ++it) {
			it->remove(true);
		}
	}
	//assert (!_path.empty());
	int rc;
	if (!isLink() && isDirectory())
		rc = rmdir(_path.c_str());
	else
		rc = unlink(_path.c_str());
	if(rc) {
		_rc = errno;
		return false;
	}
	return true;
}


bool File::createFile()
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	int n = open(_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
	if (n != -1) {
		close(n);
		return true;
	} else {
		_rc = errno;
	}	
	return false;
}


bool File::createDirectory()
{
	//assert (!_path.empty());
	if(_path.empty()) {
		return false;
	}
	if (exists() && isDirectory())
		return false;
	if (mkdir(_path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) != 0) 
		return false;
	return true;
}


bool File::createDirectories()
{
	if(_path.empty()) {
		return false;
	}
	if (!exists()) {
		Path p(_path);
		p.makeDirectory();
		if (p.depth() > 1) {
			p.makeParent();
			File f(p);
			f.createDirectories();
		}
		return createDirectory();
	}
	return false;
}


void File::list(std::vector<std::string>& files) const
{
	if(_path.empty()) {
		return ;
	}
	files.clear();
	DirectoryIterator it(*this);
	DirectoryIterator end;
	while (it != end) {
		files.push_back(it.name());
		++it;
	}
}


void File::list(std::vector<File>& files) const
{
	files.clear();
	DirectoryIterator it(*this);
	DirectoryIterator end;
	while (it != end) {
		files.push_back(*it);
		++it;
	}
}


}